home *** CD-ROM | disk | FTP | other *** search
- #! /gg/bin/perl -w
- # -*- perl -*-
- # Copyright (C) 1999--2002 Chris Vaill
- # This file is part of normalize.
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2, or (at your option)
- # any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
-
- #######################################################################
- # These variables may be customized for local setup
- #######################################################################
-
- # %m becomes name of mp3 or ogg file
- # %w becomes name of temporary WAV file
- # %b becomes bitrate of re-encoded file, as specified by the -b option
- # Example: $OGGENCODE="oggenc -Q -b %b -o %m %w"
-
- $MP3DECODE = "REPLACE_WITH_MP3_DECODER -q -o %w %m";
- $MP3ENCODE = "REPLACE_WITH_MP3_ENCODER -quiet %w %m";
- $OGGDECODE = "REPLACE_WITH_OGG_DECODER -q -d wav -f %w %m";
- $OGGENCODE = "REPLACE_WITH_OGG_ENCODER -Q -b %b -o %m %w";
-
- $VORBISCOMMENT = "REPLACE_WITH_VORBISCOMMENT";
-
- # change this if normalize is not on your path
- $NORMALIZE = "normalize";
-
-
- #######################################################################
- # No user serviceable parts below
- #######################################################################
-
- use Fcntl;
-
- sub usage {
- print <<EOF
- Usage: $progname [OPTION]... [FILE]...
- Normalize volume of mp3 or ogg files by decoding, running normalize,
- and re-encoding. This requires as much extra disk space as the
- largest mp3 or ogg file, decoded. Note that for batch and mix mode,
- all files must be decoded, so there must be enough disk space for
- the decoded copies of all specified mp3 and ogg files.
-
- -a AMP \\
- -g ADJ |
- -n |
- -T THR |_ These arguments are passed as arguments to normalize.
- -b | Run "normalize --help" for more info.
- -m |
- -v |
- -q /
-
- --bitrate BR Set bitrate of re-encoded file [default 128]
- --ogg Convert files to ogg, regardless of original format
- --mp3 Convert files to mp3, regardless of original format
- --tmpdir TMP Put temporary WAV files in temp directory TMP
- --notags Do not copy ID3 or ogg tags to the output file
- --backup Keep backups of original files, suffixed with '~'
-
- The following four options may be used to set the encoder and
- decoder commands for mp3 and ogg vorbis. \%m is expanded to the
- name of the mp3 or vorbis file, \%w expands to the name of the
- temporary WAV file, and \%b expands to the bitrate, as specified by
- the --bitrate option. The default values are shown in brackets
- below.
-
- --mp3encode=X mp3 encoder [$MP3ENCODE]
- --mp3decode=X mp3 decoder [$MP3DECODE]
- --oggencode=X ogg vorbis encoder [$OGGENCODE]
- --oggdecode=X ogg vorbis decoder [$OGGDECODE]
-
- -h Display this help and exit.
- -V Display version information and exit.
-
- Report bugs to <cvaill\@cs.columbia.edu>.
- EOF
- }
-
-
- # same effect as a backtick, but shell metacharacters are not expanded
- sub backtick_noshell {
- my @args = @_;
- my $retval = "";
- defined(my $pid = open(BABY, "-|")) || die "Can't fork: $!";
- if ($pid) {
- local $SIG{INT} = 'IGNORE';
- while (<BABY>) {
- $retval .= $_;
- }
- close BABY;
- } else {
- exec(@args) || die "Can't exec $args[0]";
- }
- $retval;
- }
-
-
- sub read_tags {
- my ($fname) = @_;
- my ($retval, $vorbis_tag, $id3v1_tag, $id3v2_tag, $id3v2_sz);
-
- if ($fname =~ /\.ogg$/i) {
- $vorbis_tag = backtick_noshell($VORBISCOMMENT, $fname);
- defined($vorbis_tag) || die "Can't run vorbiscomment: $!";
- $retval = [ 'ogg', $vorbis_tag ];
-
- } elsif ($fname =~ /\.mp3$/i) {
- open(IN, $fname) || die "Can't read $fname: $!";
- # read ID3v2 tag, if it's there
- # FIXME: doesn't work for ID3v2.4.0 appended tags
- read(IN, $id3v2_tag, 3);
- if ($id3v2_tag eq "ID3") {
- read(IN, $id3v2_tag, 7, 3);
- # figure tag size
- my ($x1, $x2, $x3, $x4) = unpack("x6 C C C C", $id3v2_tag);
- my $tagsz = $x1;
- $tagsz <<= 7;
- $tagsz += $x2;
- $tagsz <<= 7;
- $tagsz += $x3;
- $tagsz <<= 7;
- $tagsz += $x4;
- read(IN, $id3v2_tag, $tagsz, 10);
- $id3v2_sz = $tagsz + 10;
- } else {
- undef $id3v2_tag;
- $id3v2_sz = 0;
- }
- # read ID3v1 tag, if it's there
- seek(IN, -128, 2);
- read(IN, $id3v1_tag, 3);
- if ($id3v1_tag eq "TAG") {
- read(IN, $id3v1_tag, 125, 3);
- } else {
- undef $id3v1_tag;
- }
- close(IN);
-
- $retval = [ 'id3', $id3v1_tag, $id3v2_tag, $id3v2_sz ];
- }
-
- $retval;
- }
-
-
- sub write_tags {
- my ($fname, $tag) = @_;
-
- if ($fname =~ /\.ogg$/i) {
- if ($tag->[0] eq 'ogg' && $tag->[1]) {
- my @args = ($VORBISCOMMENT, "-a", $fname);
- defined(my $pid = open(BABY, "|-")) || die "Can't fork: $!";
- if ($pid) {
- local $SIG{INT} = 'IGNORE';
- print BABY $tag->[1];
- close BABY;
- $? == 0 || die "Error running vorbiscomment, stopped";
- } else {
- exec(@args) || die "Can't run vorbiscomment: $!";
- }
-
- }
-
- } elsif ($fname =~ /\.mp3$/i) {
- if ($tag->[0] eq 'id3' && $tag->[1]) {
- my $id3v1_tag = $tag->[1];
- open(OUT, ">>".$fname)
- || die "Can't append tag to $fname: $!, stopped";
- syswrite(OUT, $id3v1_tag, 128);
- close(OUT);
- }
- if ($tag->[0] eq 'id3' && $tag->[2]) {
- my ($buf, $tmpfile);
- my $id3v2_tag = $tag->[2];
- my $id3v2_sz = $tag->[3];
- my $n = $$;
- while (1) {
- $tmpfile = $tmpdir.$progname."-".$n.".tag";
- if (sysopen(OUT, $tmpfile, O_WRONLY|O_CREAT|O_EXCL)) {
- last;
- }
- $! == EEXIST || die "Can't write $tmpfile: $!, stopped";
- $n++;
- }
- syswrite(OUT, $id3v2_tag, $id3v2_sz);
- open(IN, $fname) || die "Can't read $fname: $!, stopped";
- while ($ret = sysread(IN, $buf, 4096)) {
- syswrite(OUT, $buf, $ret);
- }
- close(IN);
- close(OUT);
- unlink $fname;
- rename($tmpfile, $fname)
- || die "Can't rename temp file, leaving in $tmpfile, stopped";
- }
- }
- }
-
-
- ($progname = $0) =~ s/.*\///;
- $version = "0.7.2";
- $nomoreoptions = 0;
-
- unless ($ARGV[0]) {
- usage();
- exit 0;
- }
-
- # default option values
- @normalize_args = ($NORMALIZE, "--frontend", "-T", "0.25");
- $all_to_mp3 = 0;
- $all_to_ogg = 0;
- $bitrate = 128;
- $do_copy_tags = 1;
- $tmpdir = "";
- $do_adjust = 1;
- $batch_mode = 0;
- $mix_mode = 0;
- $keep_backups = 0;
- # we track verbosity separately for this script
- $verbose = 1;
-
- if ($VORBISCOMMENT =~ /^REPLACE/) {
- undef $VORBISCOMMENT;
- }
-
- @infnames = ();
-
- # step through arguments
- $nomoreoptions = 0;
- ARG_LOOP:
- while ($ARGV[0]) {
- if ($ARGV[0] =~ /^-/ && !$nomoreoptions) {
- $_ = $ARGV[0];
-
- if ($_ eq "-a" || $_ eq "--amplitude") {
- push @normalize_args, "-a", $ARGV[1];
- shift; shift; next ARG_LOOP;
- } elsif ($_ eq "--bitrate") {
- $bitrate = $ARGV[1];
- shift; shift; next ARG_LOOP;
- } elsif ($_ eq "-g" || $_ eq "--gain") {
- push @normalize_args, "-g", $ARGV[1];
- shift; shift; next ARG_LOOP;
- } elsif ($_ eq "-n" || $_ eq "--no-adjust") {
- push @normalize_args, "-n";
- $do_adjust = 0;
- shift; next ARG_LOOP;
- } elsif ($_ eq "-T" || $_ eq "--adjust-threshold") {
- push @normalize_args, "-T", $ARGV[1];
- shift; shift; next ARG_LOOP;
- } elsif ($_ eq "--tmp" || $_ eq "--tmpdir") {
- $tmpdir = $ARGV[1];
- if ($tmpdir !~ /\/$/) {
- $tmpdir = $tmpdir."/";
- }
- shift; shift; next ARG_LOOP;
- } elsif ($_ eq "-v" || $_ eq "--verbose") {
- push @normalize_args, "-v";
- $verbose = 2;
- shift; next ARG_LOOP;
- } elsif ($_ eq "-b" || $_ eq "--batch") {
- push @normalize_args, "-b";
- $batch_mode = 1;
- shift; next ARG_LOOP;
- } elsif ($_ eq "-m" || $_ eq "--mix") {
- push @normalize_args, "-m";
- $mix_mode = 1;
- shift; next ARG_LOOP;
- } elsif ($_ eq "-q" || $_ eq "--quiet") {
- push @normalize_args, "-q";
- $verbose = 0;
- shift; next ARG_LOOP;
- } elsif ($_ eq "--ogg") {
- $all_to_ogg = 1;
- $all_to_mp3 = 0;
- shift; next ARG_LOOP;
- } elsif ($_ eq "--mp3") {
- $all_to_mp3 = 1;
- $all_to_ogg = 0;
- shift; next ARG_LOOP;
- } elsif ($_ eq "--backup") {
- $keep_backups = 1;
- shift; next ARG_LOOP;
- } elsif ($_ eq "--notags" || $_ eq "--noid3") {
- $do_copy_tags = 0;
- shift; next ARG_LOOP;
- } elsif ($_ eq "-h" || $_ eq "--help") {
- usage;
- exit 0;
- } elsif ($_ eq "-V" || $_ eq "--version") {
- print "$progname (normalize) $version\n";
- exit 0;
- } elsif ($_ eq "--") {
- $nomoreoptions = 1;
- shift; next ARG_LOOP;
- } else {
- print "Unrecognized option \"",$ARGV[0],"\"\n";
- usage;
- exit 1;
- }
- }
-
- push(@infnames, shift);
- }
-
- if ($batch_mode || $mix_mode) {
-
- #
- # decode all files
- #
- @tmpfnames = ();
- @outfnames = ();
- for($i = 0; $i <= $#infnames; $i++) {
- $input_file = $infnames[$i];
-
- if ($input_file =~ /\.mp3$/i) {
- $decoder = $MP3DECODE;
- } elsif ($input_file =~ /\.ogg$/i) {
- $decoder = $OGGDECODE;
- } else {
- print STDERR "$progname: $input_file has unrecognized extension\n";
- print STDERR "$progname: Recognized extensions are mp3 and ogg\n";
- splice(@infnames, $i, 1);
- $i--;
- next;
- }
-
- # construct temporary file name
- # NOTE: There is a race condition here, similar to the C
- # tmpnam() function. We are ignoring it.
- ($filebase = $input_file) =~ s/^.*\///;
- $filebase = $tmpdir.$filebase;
- $n = $$;
- do {
- $tmp_file = $filebase.".".$n.".wav";
- $n++;
- } while (-e $tmp_file);
- push(@tmpfnames, $tmp_file);
- # construct output file name
- ($filebase = $input_file) =~ s/\..*?$//;
- if ($all_to_mp3) {
- $output_file = $filebase.".mp3";
- } elsif ($all_to_ogg) {
- $output_file = $filebase.".ogg";
- } else {
- $output_file = $input_file;
- }
- push(@outfnames, $output_file);
-
- # construct decode command
- @decode_args = split(/\s+/, $decoder);
- for (@decode_args) {
- s/^\%w$/$tmp_file/;
- s/^\%m$/$input_file/;
- s/^\%b$/$bitrate/;
- }
-
- # save tags
- $do_copy_tags && ($tagref = read_tags($input_file));
- push(@tags, $tagref);
-
- # run decoder
- $verbose > 0 && print STDERR "Decoding $input_file...\n";
- if ($verbose < 2) {
- open(OLDOUT, ">&STDOUT");
- open(STDOUT, ">/dev/null") || die "Can't redirect stdout";
- }
- $ret = system(@decode_args);
- if ($verbose < 2) {
- close(STDOUT);
- open(STDOUT, ">&OLDOUT");
- }
- $ret == 0 || die "Error decoding, stopped";
- }
-
-
- #
- # normalize all files
- #
- $verbose > 0 && print STDERR "Running normalize...\n";
- @args = (@normalize_args, @tmpfnames);
- $adjust_needed = 1;
- defined($pid = open(NORM, "-|")) || die "Can't fork: $!";
- if ($pid) {
- local $SIG{INT} = 'IGNORE';
- $dummy = 0; # suppress warnings about single use
- while (<NORM>) {
- if (/^ADJUST_NEEDED /) {
- ($dummy, $adjust_needed) = split;
- } elsif (/^LEVEL /) {
- unless ($do_adjust) {
- # with -n specified, the line following a LEVEL line
- # is the "level peak gain" line, so print it out
- $_ = <NORM>;
- print;
- }
- }
- }
- close NORM;
- $? == 0 || die "Error during normalize, stopped";
- } else {
- exec(@args) || die "Can't run normalize: $!";
- }
-
-
- #
- # re-encode all files
- #
- if ($do_adjust) {
- for($i = 0; $i <= $#infnames; $i++) {
- $input_file = $infnames[$i];
- $output_file = $outfnames[$i];
- $tmp_file = $tmpfnames[$i];
- $tagref = $tags[$i];
-
- # construct encode command
- $encoder = ($output_file =~ /\.ogg$/i) ? $OGGENCODE : $MP3ENCODE;
- @encode_args = split(/\s+/, $encoder);
- for (@encode_args) {
- s/^\%w$/$tmp_file/;
- s/^\%m$/$output_file/;
- s/^\%b$/$bitrate/;
- }
-
- if ($adjust_needed || $input_file ne $output_file) {
- if ($keep_backups) {
- rename($input_file, $input_file."~");
- } else {
- unlink($input_file);
- }
- # run encoder
- $verbose > 0 && print STDERR "Re-encoding $input_file...\n";
- if ($verbose < 2) {
- open(OLDOUT, ">&STDOUT");
- open(STDOUT, ">/dev/null") || die "Can't redirect stdout";
- }
- $ret = system(@encode_args);
- if ($verbose < 2) {
- close(STDOUT);
- open(STDOUT, ">&OLDOUT");
- }
- $ret == 0 || die "Error encoding, stopped";
- } else {
- $verbose > 0 && print "$input_file is already normalized, not re-encoding...\n";
- }
-
- # restore tags, if necessary
- $do_copy_tags && write_tags($output_file, $tagref);
-
- # delete temp file
- unlink $tmp_file || print STDERR "Can't remove $tmp_file: $!\n";
- }
- }
-
- exit 0;
- }
-
-
- #
- # not mix or batch mode
- #
- for $input_file (@infnames) {
-
- if ($input_file =~ /\.mp3$/i) {
- $decoder = $MP3DECODE; $encoder = $MP3ENCODE;
- } elsif ($input_file =~ /\.ogg$/i) {
- $decoder = $OGGDECODE; $encoder = $OGGENCODE;
- } else {
- print STDERR "$progname: $input_file has unrecognized extension\n";
- print STDERR "$progname: Recognized extensions are mp3 and ogg\n";
- next;
- }
-
- # construct temporary file name
- # NOTE: There is a race condition here, similar to the C
- # tmpnam() function. We are ignoring it.
- ($filebase = $input_file) =~ s{^.*/}{};
- $filebase = $tmpdir.$filebase;
- $n = $$;
- do {
- $tmp_file = $filebase.".".$n.".wav";
- $n++;
- } while (-e $tmp_file);
- # construct output file name
- #($filebase = $input_file) =~ s{(.*/)?(.*)\..*}{$2};
- ($filebase = $input_file) =~ s{\..*?$}{};
- if ($all_to_mp3) {
- $output_file = $filebase.".mp3";
- $encoder = $MP3ENCODE;
- } elsif ($all_to_ogg) {
- $output_file = $filebase.".ogg";
- $encoder = $OGGENCODE;
- } else {
- $output_file = $input_file;
- }
-
- # construct encode and decode commands
- @decode_args = split(/\s+/, $decoder);
- for (@decode_args) {
- s/^\%w$/$tmp_file/;
- s/^\%m$/$input_file/;
- s/^\%b$/$bitrate/;
- }
- @encode_args = split(/\s+/, $encoder);
- for (@encode_args) {
- s/^\%w$/$tmp_file/;
- s/^\%m$/$input_file/;
- s/^\%b$/$bitrate/;
- }
-
- # save tags
- $do_copy_tags && ($tagref = read_tags($input_file));
-
-
- #
- # run decoder
- #
- $verbose > 0 && print STDERR "Decoding $input_file...\n";
- if ($verbose < 2) {
- open(OLDOUT, ">&STDOUT");
- open(STDOUT, ">/dev/null") || die "Can't redirect stdout";
- }
- $ret = system(@decode_args);
- if ($verbose < 2) {
- close(STDOUT);
- open(STDOUT, ">&OLDOUT");
- }
- $ret == 0 || die "Error decoding, stopped";
-
-
- #
- # run normalize
- #
- $verbose > 0 && print STDERR "Running normalize...\n";
- @args = (@normalize_args, $tmp_file);
- $adjust_needed = 1;
- defined($pid = open(NORM, "-|")) || die "Can't fork: $!";
- if ($pid) {
- local $SIG{INT} = 'IGNORE';
- $dummy = 0; # suppress warnings about single use
- while (<NORM>) {
- if (/^ADJUST_NEEDED /) {
- ($dummy, $adjust_needed) = split;
- } elsif (/^LEVEL /) {
- unless ($do_adjust) {
- # with -n specified, the line following a LEVEL line
- # is the "level peak gain" line, so print it out
- $_ = <NORM>;
- print;
- }
- }
- }
- close NORM;
- $? == 0 || die "Error during normalize, stopped";
- } else {
- exec(@args) || die "Can't run normalize: $!";
- }
-
-
- #
- # run encoder, if necessary
- #
- if ($do_adjust) {
- if ($adjust_needed || $input_file ne $output_file) {
- if ($keep_backups) {
- rename($input_file, $input_file."~");
- } else {
- unlink($input_file);
- }
- # run encoder
- $verbose > 0 && print STDERR "Re-encoding $input_file...\n";
- if ($verbose < 2) {
- open(OLDOUT, ">&STDOUT");
- open(STDOUT, ">/dev/null") || die "Can't redirect stdout";
- }
- $ret = system(@encode_args);
- if ($verbose < 2) {
- close(STDOUT);
- open(STDOUT, ">&OLDOUT");
- }
- $ret == 0 || die "Error encoding, stopped";
- } else {
- $verbose > 0 && print "$input_file is already normalized, not re-encoding...\n";
- }
- }
-
- # restore tags, if necessary
- $do_copy_tags && write_tags($output_file, $tagref);
-
- # delete temp file
- unlink $tmp_file || print STDERR "Can't remove $tmp_file: $!\n";
- }
-